New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
esp8266: Add machine.time_pulse_us function, to time input pulses. #2091
Conversation
First of all, I appreciate following my idea of making it a standalone functional algorithm along the line of functions like len(), sorted(), etc. And I don't think that what you say above is true. Or rather, it can be true only because we didn't implement Pin API completely and in its full power. So, how about we make this patch work on unix port, and be able to time any Pin class defined in Python? |
Thanks for this!
Any thoughts on my suggestion in #2052 to support not first waiting for the input pin to be different from the current level? It'd be trivial to add and this way pulses that have already started could be timed too. |
+1. Consider you DHT considerations: storing read value into an array should take less than 50us. What if it takes 52us ans sensor already started a new pulse? As implemented now, everything will break, With an option suggested by @roger- , it will work. |
Yup, the DHT is exactly what I had in mind! |
One problem is arg list bloat. Can replace "level" to be bitmask of options instead. |
It already does this. In fact, this is the only behaviour it has: "It first waits while the pin input is different to the given level, then times the duration that the pin is equal to the given level". For example, if the argument |
Yeah, how about a trigger mask: TRIG_VALUE = 0 # trigger on pin value, like current code
TRIG_EDGE = 2 # start timing on given edge, rising or falling
machine.time_pulse_us(pin, 1 | TRIG_EDGE) # time a rising -> falling pulse |
Ah right, didn't look closely at your code. I can imagine cases where that behavior might not be desired though, and the user might prefer to get a timeout error if the pulse is missing both edges. |
One of the better results on a overloaded Linux system:
|
@dpgeorge : Consequently please move the implementation to extmod/machine_pulse.c |
I still don't see how time_pulse_us can be a universal function. As it's written it requires a pin object as the first argument so it may as well be a pin method (eg in PinBase's method list). If it wants to be universal then it should call value() on the incoming "pin" object, instead of using mp_pin_hal_read. But that will be terribly inefficient. One would need to have a special case to check for a pin object, and a general case to call value(). |
Well, it already is, it would take additional steps to not make it so.
Arbitrary pin type class is not subclass of PinBase, so PinBase having that method is of no help to them. We also don't use such pattern anywhere else, and this thing wouldn't be the best to start with (inheritance in non-compiled language == overhead, hardware operations IN_NEED_OF low overhead). Finally, we can't do that right away anyway (inheritance is not supported for native type), so we would go a long-long way to make people add more characters to implement pin type in C then really needed, make it consume more bytes, and run for more cycles. What for? |
For ports that don't use PinBase, they would put the time_pulse_us method directly in the method table of the pin object that they define. So there's no overhead there, and it'll just work as-is. So my proposal is:
|
How is there no overhead, if it requires:
|
There's no computation overhead when doing the operation. Making it a function puts overhead on the usage as the user must know where it is (which module is sensible for it?). It's easier (more intuitive) from the user point of view to have it as a method. |
And while I agree that per "classical type-based OO approach", this function would indeed be a method in an abstract base class, from which every concrete class would need to inherit, "classical type-based" approach is not the only approach to OO. Languages like Python or C++ heavily use "ducktyping approach", where there's no formal requirement for inheritance from a common base class (such requirement poses multiple inheritance issues). Instead, part of their OO model is based on the concept of "protocol" (Python term). With it, anything which provides methods required by a protocol is instance of that protocol. Operations which work om protocol classes/instances are called "algorithms" in C++ terms, implemented as a standalone functions, and C++ offers whole bunch of them: So, while time_pulse_us() could be a Pin class method, there's no should if that's the concern. We continue MicroPython library of Pin algorithms, if which there're already (not fully generalized) I2C and SPI examples. |
(continuing) Quite unsurprisingly, bitbanging I2C and SPI are not methods of a Pin class, because it would be quite strange to call it as a method on one pin, and pass other as params. Also, both I2C and SPI are complex algos, so they aren't just functions, but classes. So, it requires just a one final small step to note that a function like time_pulse_us has much more in common with algos like SPI, I2C, and bunch of other we may add, than with Pin methods like .on(), .off(), .get() (or .value() with subsumes them all). The benefit of this understanding is that Pin class stays very simple, both conceptually and implementation-wise, with obvious benefit to MicroPython's reach out. If we do it right, we will be copied by other people, when they in couple of years grasp all teh benefit of a truly layered OO model, instead of "make everything a method" one. |
It's obvious - machine.
Just as treating CS part of SPI bus (what it isn't) or round Pi to 3. We shouldn't look for oversimplifying solutions, but ones which enable more powerful usage. Simplicity from them comes at different level (in Pin class). |
|
Ok, your arguments are good (in particular "it requires just a one final small step to note that a function like time_pulse_us has much more in common with algos like SPI, I2C, and bunch of other we may add, than with Pin methods"). So let's go for a function in the machine module. I guess we stick with the time_pulse_us name? To leave room for time_pulse_ms and time_pulse (in seconds) if needed? |
While watching this debate occurred to me to implement also the popular functions from the world of Arduino...
There is a number of situations where they are very useful. |
Thanks!
Yes, IMHO the name is perfectly ok, and is self-descriptive, even if we won't ever get time_pulse_ms, etc. I'm less sure about timeout arg. By default for this single function it would be in microseconds, but if we add other similar functions, such "local least surprise" may lead to inconsistency. Above you say that other funcs take timeout in ms, if you're sure that's indeed the case (for new API), then maybe it's good idea to go that way for this func too. |
These are just SPI commands. The machine.SPI object should suffice for such cases. |
When implementing the DHT driver I needed a C version of time_pulse_us, so it's already done here: https://github.com/micropython/micropython/blob/master/drivers/dht/dht.c#L34-L48 (this will move to extmod/machine_pulse.c and be a public function at the C level, plus a small Python wrapper). You can see in the DHT driver that it uses timeouts of the order 100us. This is because if the device takes longer than that then it's not functioning / not connected. Having 1ms as the minimum timeout could make it slower to detect a bad device. But if you're calling this code from Python then 1ms would be ok as the minimum timeout. So maybe at the C level the function takes a timeout in microseconds, but at the Python level in milliseconds? |
I'll leave this to you, but if there's already usecase for sub-ms timeout for this function, I'd say it should take us, regardless if it's Py or C. |
It's a timing function measuring microsecond pulses, so it makes sense to have the timout also measured in microseconds. I'll leave the timeout as us then. |
Reworked and push in 4940bee, 93a9c2e and cff2b7a. From my comment above please note:
Also, if we want to add the ability to wait for the starting edge of the pulse then that can be added later as per the discussion above using bit-flags to the pulse_level argument (in the docs I've specified this argument as needing to be either 0 or 1 to allow for a future enhancement). |
Thanks! We definitely should consider this function "provisional" (in CPython terms) and expect that it's interface may be tweaked. |
Samd dma tracking
Here is a proposal for a function/method to time input pulses on a pin.
Current API is:
level
is the level on the pin to measure a pulse of (eg level=1 means measure a high pulse). All units are in microseconds (although timeout could probably be converted to milliseconds for compatibility with other timeout args).It first waits while the pin input is different to the given level, then times the duration that the pin is equal to the given level. Will raise an OSError if either of those waits is longer than the given timeout value.
This function could easily be made a method of the Pin class (no change to the C code would be needed). It seems sensible to make it a method of Pin because the function does not work on any other objects.
Note also that if the timeout is too large (eg 10 seconds) and no pulse is detected, then the ESP will reset due to WDT timeout. It's not clear how to fix this while still retaining accuracy of timing the pulse (and also be portable to other ports).
Tested by making a PWM output on one pin and connecting it to another, and timing the PWM pulses.
See #2052.